using UnityEngine;
using System.Collections;

public class Branch2D
{
    public Vector3 location;
    public Branch2D parent;
    public Vector3 growDir = new Vector3(0.0f, 0.0f, 0.0f);
    public int growCount = 0;
    public float area = 0.0f;
    public float finalArea = 0f;
    public int leafNumber = 1;

    // Growth variables
    public bool alive = true;

    // Branch Mesh
    public int numVertices = 6;
    public float radiusTop = 0.01f;
    public float radiusBottom = 0.01f;
    public float length = 1f;
    public float openingAngle = 0f; // if >0, create a cone with this angle by setting radiusTop to 0, and adjust radiusBottom according to length;
    public bool addCollider = false;
    public GameObject coneObject;
    public GameObject sphereObject;
    public GameObject planeObject;
    public MeshFilter meshFilter;
    public Vector3[] vertices;
    Vector3[] normals;
    Vector2[] uvs;
    int[] tris;
    public Mesh mesh;

    public Branch2D()
    {
    }

    public Branch2D(Vector3 position)
    {
        location = position;
    }

    public Branch2D(Vector3 position, Branch2D parent, Transform parentObject, float length, int subdivisions)
    {
        location = position;
        this.parent = parent;
        if (parent != null)
        {
            coneObject = new GameObject();
            coneObject.transform.parent = parentObject;
            coneObject.transform.position = location;

            //Vector3 targetPostition = new Vector3(parent.location.x, parent.location.y, coneObject.transform.position.z);

            coneObject.transform.LookAt(parent.location);
            coneObject.transform.eulerAngles = new Vector3(coneObject.transform.eulerAngles.x, coneObject.transform.eulerAngles.y, 0f);

            this.numVertices = subdivisions;
            this.length = length;
            this.radiusTop = 0.01f;
            this.radiusBottom = 0.01f;
            CreateSegment();

            leafNumber = Random.Range(1, 1);
        }
        else
        {
            // Central Root
            coneObject = new GameObject();
            coneObject.name = "Base Root";
            coneObject.transform.parent = parentObject;
            coneObject.transform.position = location;

            coneObject.transform.LookAt(new Vector3(location.x, location.y - length, location.z));

            this.numVertices = subdivisions;
            this.length = length;
            this.radiusTop = 1f;
            this.radiusBottom = 1f;
            CreateSegment();

            leafNumber = Random.Range(1, 1);
        }
    }

    public void Draw(float thickness)
    {
        if (parent != null)
        {
            if (area == 0.0f)
                area = 0.1f;

            parent.area += area;

            //coneObject.transform.localScale = new Vector3(coneObject.transform.localScale.x + Mathf.Sqrt(area * thickness), coneObject.transform.localScale.y + Mathf.Sqrt(area * thickness), coneObject.transform.localScale.z);
            this.radiusBottom = Mathf.Sqrt(parent.area * thickness);
            this.radiusTop = Mathf.Sqrt(this.area * thickness);
            //UpdateSegment();
            finalArea = area;
            
            //if (!TreeCraft.drawLeaves)
            area = 0.0f;
        }
        /*else
        {
            if (area == 0.0f)
                area = 1.0f;

            this.radiusBottom = Mathf.Sqrt(this.area * thickness);
            this.radiusTop = Mathf.Sqrt(this.area * thickness);

            if (!TreeCraft.drawLeaves)
                area = 0.0f;
        }*/
    }

    public void DrawLeaves(Transform parentObject, float heightStart)
    {
        if (this.location.y >= heightStart)
        {
            float leafSize = Random.Range(2f, 5f);
            /*planeObject = Primitive.CreatePlaneGameObject(leafSize, leafSize, 1, 1);
            planeObject.transform.parent = parentObject;
            planeObject.transform.position = new Vector3(location.x, location.y, location.z);
            planeObject.transform.rotation = Random.rotation;*/
            //coneObject.transform.LookAt(this.coneObject.transform.position);
        }
    }

    public void FinalizeSegment()
    {
        float slope = Mathf.Atan((radiusBottom - radiusTop) / length); // (rad difference)/height
        float slopeSin = Mathf.Sin(slope);
        float slopeCos = Mathf.Cos(slope);
        int i;

        for (i = 0; i < numVertices; i++)
        {
            float angle = 2 * Mathf.PI * i / numVertices;
            float angleSin = Mathf.Sin(angle);
            float angleCos = Mathf.Cos(angle);

            vertices[i] = new Vector3(radiusTop * angleCos, radiusTop * angleSin, 0);
            if (parent == null)
                vertices[i + numVertices] = new Vector3(radiusBottom * angleCos, radiusBottom * angleSin, length);
            else
                vertices[i + numVertices] = new Vector3(radiusBottom * angleCos, radiusBottom * angleSin, length);

            normals[i] = new Vector3(angleCos * slopeCos, angleSin * slopeCos, -slopeSin);
            normals[i + numVertices] = new Vector3(angleCos * slopeCos, angleSin * slopeCos, -slopeSin);

            uvs[i] = new Vector2(1.0f * i / numVertices, 1);
            uvs[i + numVertices] = new Vector2(1.0f * i / numVertices, 0);
        }

        // create triangles
        // here we need to take care of point order, depending on inside and outside
        int cnt = 0;

        // truncated cone
        tris = new int[numVertices * 6];

        for (i = 0; i < numVertices; i++)
        {
            int ip1 = i + 1;
            if (ip1 == numVertices)
                ip1 = 0;

            tris[cnt++] = i;
            tris[cnt++] = ip1;
            tris[cnt++] = i + numVertices;

            tris[cnt++] = ip1 + numVertices;
            tris[cnt++] = i + numVertices;
            tris[cnt++] = ip1;
        }

        mesh.vertices = vertices;
        mesh.normals = normals;
        mesh.uv = uvs;
        mesh.triangles = tris;

        mesh.RecalculateNormals();
        mesh.RecalculateBounds();
        calculateMeshTangents(mesh);
    }

    public void CreateSpheres(int subdivisions)
    {
        /*sphereObject = Primitive.CreateSphereGameObject(this.radiusTop, subdivisions, 10);
        sphereObject.transform.parent = this.coneObject.transform;
        sphereObject.transform.position = new Vector3(location.x, location.y, location.z);

        sphereObject.transform.LookAt(this.coneObject.transform.position);*/
        //sphereObject.transform.eulerAngles = new Vector3(sphereObject.transform.eulerAngles.x, sphereObject.transform.eulerAngles.y, coneObject.transform.eulerAngles.z);
    }

    public void UpdateSegment()
    {
        float slope = Mathf.Atan((radiusBottom - radiusTop) / length); // (rad difference)/height
        float slopeSin = Mathf.Sin(slope);
        float slopeCos = Mathf.Cos(slope);
        int i;

        for (i = 0; i < numVertices; i++)
        {
            float angle = 2 * Mathf.PI * i / numVertices;
            float angleSin = Mathf.Sin(angle);
            float angleCos = Mathf.Cos(angle);

            vertices[i] = new Vector3(radiusTop * angleCos, radiusTop * angleSin, 0);
            vertices[i + numVertices] = new Vector3(radiusBottom * angleCos, radiusBottom * angleSin, length);

            normals[i] = new Vector3(angleCos * slopeCos, angleSin * slopeCos, -slopeSin);
            normals[i + numVertices] = new Vector3(angleCos * slopeCos, angleSin * slopeCos, -slopeSin);

            uvs[i] = new Vector2(1.0f * i / numVertices, 1);
            uvs[i + numVertices] = new Vector2(1.0f * i / numVertices, 0);
        }

        // create triangles
        // here we need to take care of point order, depending on inside and outside
        int cnt = 0;

        // truncated cone
        tris = new int[numVertices * 6];

        for (i = 0; i < numVertices; i++)
        {
            int ip1 = i + 1;
            if (ip1 == numVertices)
                ip1 = 0;

            tris[cnt++] = i;
            tris[cnt++] = ip1;
            tris[cnt++] = i + numVertices;

            tris[cnt++] = ip1 + numVertices;
            tris[cnt++] = i + numVertices;
            tris[cnt++] = ip1;
        }
    }

    public void CreateSegment()
    {
        mesh = new Mesh();

        vertices = new Vector3[2 * numVertices]; // 0..n-1: top, n..2n-1: bottom
        normals = new Vector3[2 * numVertices];
        uvs = new Vector2[2 * numVertices];

        MeshFilter mf = coneObject.AddComponent<MeshFilter>();
        mf.mesh = mesh;
    }

    void calculateMeshTangents(Mesh mesh)
    {

        //speed up math by copying the mesh arrays
        int[] triangles = mesh.triangles;
        Vector3[] vertices = mesh.vertices;
        Vector2[] uv = mesh.uv;
        Vector3[] normals = mesh.normals;

        //variable definitions
        int triangleCount = triangles.Length;
        int vertexCount = vertices.Length;

        Vector3[] tan1 = new Vector3[vertexCount];
        Vector3[] tan2 = new Vector3[vertexCount];

        Vector4[] tangents = new Vector4[vertexCount];

        for (long a = 0; a < triangleCount; a += 3)
        {
            long i1 = triangles[a + 0];
            long i2 = triangles[a + 1];
            long i3 = triangles[a + 2];

            Vector3 v1 = vertices[i1];
            Vector3 v2 = vertices[i2];
            Vector3 v3 = vertices[i3];

            Vector2 w1 = uv[i1];
            Vector2 w2 = uv[i2];
            Vector2 w3 = uv[i3];

            float x1 = v2.x - v1.x;
            float x2 = v3.x - v1.x;
            float y1 = v2.y - v1.y;
            float y2 = v3.y - v1.y;
            float z1 = v2.z - v1.z;
            float z2 = v3.z - v1.z;

            float s1 = w2.x - w1.x;
            float s2 = w3.x - w1.x;
            float t1 = w2.y - w1.y;
            float t2 = w3.y - w1.y;

            float r = 1.0f / (s1 * t2 - s2 * t1);

            Vector3 sdir = new Vector3((t2 * x1 - t1 * x2) * r, (t2 * y1 - t1 * y2) * r, (t2 * z1 - t1 * z2) * r);
            Vector3 tdir = new Vector3((s1 * x2 - s2 * x1) * r, (s1 * y2 - s2 * y1) * r, (s1 * z2 - s2 * z1) * r);

            tan1[i1] += sdir;
            tan1[i2] += sdir;
            tan1[i3] += sdir;

            tan2[i1] += tdir;
            tan2[i2] += tdir;
            tan2[i3] += tdir;
        }

        for (long a = 0; a < vertexCount; ++a)
        {
            Vector3 n = normals[a];
            Vector3 t = tan1[a];

            //Vector3 tmp = (t - n * Vector3.Dot(n, t)).normalized;
            //tangents[a] = new Vector4(tmp.x, tmp.y, tmp.z);
            Vector3.OrthoNormalize(ref n, ref t);
            tangents[a].x = t.x;
            tangents[a].y = t.y;
            tangents[a].z = t.z;

            tangents[a].w = (Vector3.Dot(Vector3.Cross(n, t), tan2[a]) < 0.0f) ? -1.0f : 1.0f;
        }

        mesh.tangents = tangents;
    }
}
